// Client.cpp : 定义控制台应用程序的入口点。
// g++编译需加入编译选项 -lwsock32

// #include "stdafx.h"
#include <Winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include "conio.h"

#pragma comment(lib, "ws2_32.lib")

// define constant
#define STRLEN 4096
#define SERVPORT 5400
#define CLIUSERLEN 8
#define WSVERS MAKEWORD(2, 2)
#define FILEPORT 7400
#define USER_NOT_EXISTS "400"
#define INCORRECT_PASSWORD "401"
#define USER_NOT_FOUND "4040"
#define NOT_ONLINE "4041"
#define REJECT_CHATTING "4042"
#define USER_IS_LOCK "4043"
#define IS_ONLINE "2000"
#define START_CHATTING "2001"

unsigned WINAPI sendmsg(void * linksock);
unsigned WINAPI recvmsg(void * linksock);

char tempBuf[STRLEN]; // global string buffer
int recvThreadBlock = 0;
int inGroup = 1;
char username[CLIUSERLEN];

void testSend(int messageLen);

int logIn(SOCKET linksock);

void oneToOneChatStart(SOCKET linksock);

void chat(SOCKET linksock);

void groupChat(SOCKET linksock);

void recvFile(SOCKET linksock);

void sendFile(SOCKET linksock);

void call(SOCKET linksock);

int main(int argc, char *argv[]) {
    SOCKET   cli;
    struct   sockaddr_in servSockIn;
    HANDLE   hsendThread, hrecvThread;
    WSADATA  wsadata;
    int      recvlen;
    if (WSAStartup(WSVERS, &wsadata) != 0) {
        printf("failed to load win socket\n");
        return 1;
    }

    char ipAddr[20] = "127.0.0.1";

    // server address
    memset(&servSockIn, 0, sizeof(servSockIn));
    servSockIn.sin_family = AF_INET;
    servSockIn.sin_addr.s_addr = inet_addr(ipAddr);
    // servSockIn.sin_addr.s_addr = inet_addr(argv[1]);
    servSockIn.sin_port = htons(SERVPORT);

    // create socket
    cli = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (cli == INVALID_SOCKET) {
        printf("socket() failed: %d\n", WSAGetLastError());
        return 1;
    }

    // connect to server
    printf("connect to %s:%d\n", inet_ntoa(servSockIn.sin_addr), ntohs(servSockIn.sin_port));

    if (connect(cli, (SOCKADDR*)&servSockIn, sizeof(servSockIn)) == INVALID_SOCKET) {
        printf("connection failed: %d\n", WSAGetLastError());
        return 1;
    }
    else {
        printf("connected...\n");
    }

    // log in
    if (logIn(cli) < 0) {
        // log in failed.
        printf("log in failed. This cli will quit.\n");
        return 1;
    }

    printf("log in successfully!\n");

    // get online user list
    memset(&tempBuf, 0, sizeof(tempBuf));
    recvlen = recv(cli, tempBuf, sizeof(tempBuf), 0);
    if (recvlen > 0) printf("%s", tempBuf);

    // chating
    hsendThread = (HANDLE)_beginthreadex(NULL, 0, &sendmsg, (void *)&cli, 0, NULL);
    hrecvThread = (HANDLE)_beginthreadex(NULL, 0, &recvmsg, (void *)&cli, 0, NULL);

    // wait recv thread and send thread
    WaitForSingleObject(hsendThread, INFINITE);
    WaitForSingleObject(hrecvThread, INFINITE);

    // log out
    closesocket(cli);
    WSACleanup();

    return 0;
}

// send-message thread
unsigned WINAPI sendmsg(void * linksock) {
    SOCKET  hsock = *((SOCKET*)linksock);
    char    wBuf[STRLEN];
    int     messageLen;

    while (true) {
        if (inGroup) {
            fputs("Please input a function: \n", stdout);
            fputs("'quit' to escape\n", stdout);
            fputs("'chat' with someone \n", stdout);
            fputs("'call' someone(to transport file) \n", stdout);
            fputs("'list' all online users \n", stdout);
        } 

        fgets(wBuf, STRLEN, stdin);

        if (strstr(wBuf, "call confirm\n") != NULL) {
            // confirm to recv file
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "call confirm");
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            recvFile(hsock);
        }
        
        if (strstr(wBuf, "chat confirm\n") != NULL) {
            // confirm to start chatting by request
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "chat confirm");
            inGroup = 0;
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            oneToOneChatStart(hsock);
            inGroup = 1;
        }
        if (strstr(wBuf, "chat reject\n") != NULL) {
            // reject chatting request
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "chat reject");
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
        }

        if (strstr(wBuf, "chat\n") != NULL) {
            // request to chat with someone or build a group
            chat(hsock);
        }
        else if (strstr(wBuf, "list\n") != NULL) {
            // list-all-user request
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "list");
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
        }
        else if (strncmp(wBuf, "group\n", 7) == 0) {
            // confirm to join a group
            inGroup = 0;
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);
        }
        else if (strncmp(wBuf, "call\n", 7) == 0) {
            // request a connecttion for file transport
            call(hsock);
        }
        else {

            // other message
            messageLen = send(hsock, wBuf, strlen(wBuf), 0);
            testSend(messageLen);

            // log out
            if (strncmp(wBuf, "quit\n", 7) == 0) {
                printf("log out successfully. \n");
                break;
            }
        }
    }

    closesocket(hsock);
    return 0;
}

// recv-message thread
unsigned WINAPI recvmsg(void * linksock) {
    SOCKET  lsock = *((SOCKET*)linksock);
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    struct  sockaddr_in cln_addr;
    int     cln_addr_len;

    while (true) {
        // block for send thread
        while (recvThreadBlock) {
            continue;
        }

        memset(rBuf, 0, sizeof(rBuf));
        int recvlen = recv(lsock, rBuf, STRLEN, 0);
        if (recvlen == SOCKET_ERROR || recvlen == 0) {
            break;
        }
        else if (recvlen > 0) {

            if (strncmp(rBuf, "quit group", 11) == 0) {
                printf("quit group successfully. \n");
                memset(wBuf, 0, sizeof(wBuf));
                sprintf(wBuf, "quit group\n");
                testSend(send(lsock, wBuf, strlen(wBuf), 0));
                inGroup = 1;
                continue;
            }

            rBuf[recvlen] = '\0';
            printf("%s", rBuf);
        }
    }

    closesocket(lsock);
    return 0;
}

// whether a message send successfully or not
void testSend(int messageLen) {
    switch (messageLen) {
    case SOCKET_ERROR: printf("send() failed: %d\n", WSAGetLastError());
    case 0: break;
    default: printf("send successfully. \n", messageLen);
    }
    return;
}

// user log in
int logIn(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    rBuf[STRLEN];
    char    wBuf[STRLEN];
    struct  sockaddr_in cln_addr;
    int     cln_addr_len;
    int     logState;
    int     messageLen;
    int     recvlen;

    // send username
    memset(wBuf, 0, sizeof(wBuf));
    fputs("Please input username: ", stdout);
    fgets(wBuf, sizeof(wBuf), stdin);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // recv reply
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);

    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        memset(wBuf, 0, sizeof(wBuf));
        sprintf(wBuf, "log in failed. \n");
        printf("%s", wBuf);
        return -1;
    }
    else if (recvlen > 0) {
        // user is already online
        if (strstr(rBuf, USER_IS_LOCK) != NULL) {
            printf("user is already online. \n");
            return -1;
        }
        // user not exists.
        logState = (strstr(rBuf, USER_NOT_EXISTS) != NULL);
        switch (logState) {
        case 1: printf("user not exists. \n"); return -1; break;
        default: printf("%s", rBuf);
        }

        // send password
        memset(wBuf, 0, sizeof(wBuf));
        fgets(wBuf, sizeof(wBuf), stdin);
        messageLen = send(lsock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);

        // recv log state
        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            memset(wBuf, 0, sizeof(wBuf));
            sprintf(wBuf, "log in failed. \n");
            printf("%s", wBuf);
            return -1;
        }
        else if (recvlen > 0) {
            logState = (strstr(rBuf, INCORRECT_PASSWORD) != NULL);
            switch (logState) {
            case 1: printf("incorrect password. \n"); return -1; break;
            default: printf("correct password. \n");
            }

            printf("%s", rBuf);
            return 1;
        }
    }
}

// chat request
void chat(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    wBuf[STRLEN];
    char    rBuf[STRLEN];
    int     messageLen;
    int     recvlen;
    recvThreadBlock = 1; // block recv thread

    // send chat request
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "chat");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // wait for reply from server
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("start chatting failed: response not received. \n");
        return ;
    }
    else if (recvlen > 0) {
        // recv chat choice request
        rBuf[recvlen] = '\0';
        printf("%s", rBuf);
    }

    // send choice
    memset(wBuf, 0, sizeof(wBuf));
    fgets(wBuf, STRLEN, stdin);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    if (strstr(wBuf, "one") != NULL) {
        // one-to-one chatting
        
        // wait for destination username request
        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            printf("start chatting failed: destination request not received. \n");
            recvThreadBlock = 0; // release lock
            return;
        }
        else if (recvlen > 0) {
            // recv destination username request
            rBuf[recvlen] = '\0';
            printf("%s", rBuf);
        }

        // send destination username
        memset(wBuf, 0, sizeof(wBuf));
        fgets(wBuf, STRLEN, stdin);
        messageLen = send(lsock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);
        wBuf[messageLen - 1] = '\0';

        // wait for destination username response
        memset(rBuf, 0, sizeof(rBuf));
        recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
        if (recvlen == 0 || recvlen == SOCKET_ERROR) {
            printf("start chatting failed: destination response not received. \n");
            recvThreadBlock = 0; // release lock
            return;
        }
        else if (recvlen > 0) {
            rBuf[recvlen] = '\0';
            // diff response code

            // destination user not found
            if (strstr(rBuf, USER_NOT_FOUND) != NULL) {
                printf("user %s is not found. \n", wBuf);
                recvThreadBlock = 0; // release lock
                return;
            }
            // destination user not online
            else if (strstr(rBuf, NOT_ONLINE) != NULL) {
                printf("user %s isn't online. \n", wBuf);
                recvThreadBlock = 0; // release lock
                return;
            }
            else if (strstr(rBuf, USER_IS_LOCK) != NULL) {
                printf("user %s is busy now. \n", wBuf);
                recvThreadBlock = 0; // release lock
                return;
            }

            else if (strstr(rBuf, IS_ONLINE) != NULL) {
                printf("user %s is online. \n", wBuf);

                // wait for destination user chat-confirm
                memset(rBuf, 0, sizeof(wBuf));
                recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
                if (recvlen == 0 || recvlen == SOCKET_ERROR) {
                    printf("start chatting failed: chatting confirm not received. \n");
                    recvThreadBlock = 0; // release lock
                    return;
                }
                else if (recvlen > 0) {
                    if (strstr(rBuf, REJECT_CHATTING) != NULL) {
                        // chatting rejected
                        printf("but rejected your chatting request. \n");
                        recvThreadBlock = 0; // release lock
                        return;
                    }
                    else if (strstr(rBuf, START_CHATTING) != NULL) {
                        // one-to-one chatting
                        oneToOneChatStart(lsock);
                    }
                }
            }
        }
    }
    else if (strstr(rBuf, "group") != NULL) {
        // more-to-more chatting
        groupChat(lsock);
    }
    else {
        // other chat-type input
        printf("start chatting failed: chat-type not supported. \n");
    }

    recvThreadBlock = 0; // release lock
    return;
}

void oneToOneChatStart(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    wBuf[STRLEN];
    int     messageLen;
    recvThreadBlock = 0; // make sure recv thread not locked

    while (true) {
        fputs("chatting...('quit' ot quit)\n", stdout);
        fgets(wBuf, STRLEN, stdin);
        int messageLen = send(lsock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);

        // quit chatting
        if (strncmp(wBuf, "quit\n", 7) == 0) {
            printf("you leave chatting. \n");
            break;
        }
    }

    return;
}

void groupChat(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    wBuf[STRLEN];
    char    rBuf[STRLEN];
    int     messageLen;
    int     recvlen;
    inGroup = 0; // in a group

    // recv response
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("group chat request failed: no response\n");
        return ;
    } else if (recvlen > 0) {
        // recv group members' names request
        rBuf[recvlen] = '\0';
        printf("%s", rBuf);
    }

    // send group members' names
    memset(wBuf, 0, sizeof(wBuf));
    fgets(wBuf, STRLEN, stdin);
    while (strncmp(wBuf, ".", 1) != 0) {
        messageLen = send(lsock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);
        memset(wBuf, 0, sizeof(wBuf));
        fgets(wBuf, STRLEN, stdin);
    }
    // group members' names send end.
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    recvThreadBlock = 0; // unlock recv thread

    // group-chatting begin
    printf("group chatting begin...\n");
    while (true) {
        memset(wBuf, 0, sizeof(wBuf));
        fgets(wBuf, STRLEN, stdin);
        messageLen = send(lsock, wBuf, strlen(wBuf), 0);
        testSend(messageLen);
        // quit group
        if (inGroup) break;
    }

    inGroup = 1; // leave a group
    return ;
}

void call(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    wBuf[STRLEN];
    char    rBuf[STRLEN];
    int     messageLen;
    int     recvlen;
    recvThreadBlock = 1; // lock recv thread
    inGroup = 0; // block send thread 

    // send call request
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "call");
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // wait for reply from server
    /* memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("call error: server no response\n");
        return ;
    } else if (recvlen > 0) {
        printf("%s", rBuf);
    } */

    // send called-user's name
    memset(wBuf, 0, sizeof(wBuf));
    fgets(wBuf, STRLEN, stdin);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // wait for destination-username response
    memset(rBuf, 0, sizeof(rBuf));
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("call failed: destination response not received. \n");
        recvThreadBlock = 0; // release lock
        inGroup = 1;
        return;
    }
    else if (recvlen > 0) {
        wBuf[messageLen - 1] = '\0';
        // diff response code

        // destination user not found
        if (strstr(rBuf, USER_NOT_FOUND) != NULL) {
            printf("user %s is not found. \n", wBuf);
            recvThreadBlock = 0; // release lock
            inGroup = 1;
            return;
        }
        // destination user not online
        else if (strstr(rBuf, NOT_ONLINE) != NULL) {
            printf("user %s isn't online. \n", wBuf);
            recvThreadBlock = 0; // release lock
            inGroup = 1;
            return;
        }
        // user is busy
        else if (strstr(rBuf, USER_IS_LOCK) != NULL) {
            printf("user %s is busy now. \n", wBuf);
            recvThreadBlock = 0; // release lock
            inGroup = 1;
            return;
        }

        else if (strstr(rBuf, IS_ONLINE) != NULL) {
            printf("user %s is online. \n", wBuf);

            // wait for call confirmed
            memset(rBuf, 0, sizeof(rBuf));
            recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
            if (recvlen == 0 || recvlen == SOCKET_ERROR) {
                printf("start chatting failed: chatting confirm not received. \n");
                recvThreadBlock = 0; // release lock
                inGroup = 1;
                return;
            }
            else if (recvlen > 0) {
                // transport file
                sendFile(lsock);

                // tell server file transport finished
                memset(wBuf, 0, sizeof(wBuf));
                sprintf(wBuf, "end transport.");
                messageLen = send(lsock, wBuf, strlen(wBuf), 0);
                testSend(messageLen);
            }
        }
    }

    inGroup = 1;
    recvThreadBlock = 0; //unlock
    return ;
}

void recvFile(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    wBuf[STRLEN];
    char    rBuf[STRLEN];
    int     messageLen;
    int     recvlen;

    // send recv socket port number
    memset(wBuf, 0, sizeof(wBuf));
    sprintf(wBuf, "%d", FILEPORT);
    messageLen = send(lsock, wBuf, strlen(wBuf), 0);
    testSend(messageLen);

    // file server socket build
    WSADATA wsaData;
    WSAStartup(WSVERS, &wsaData);
    SOCKET servSock = socket(PF_INET, SOCK_STREAM, 0);

    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    sockAddr.sin_port = htons(FILEPORT);
    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    listen(servSock, 5);printf("wait");

    SOCKADDR clntAddr;
    int nSize = sizeof(SOCKADDR);
    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
    printf("contected. ");
    printf("watting for filename to recv...\n");

    // input filename and create it
    char filename[STRLEN] = {0};
    printf("Input filename to save: ");
    fgets(filename, STRLEN, stdin);
    int len = strlen(filename);
    filename[len - 1] = '\0';
    FILE *fp = fopen(filename, "wb");  //以二进制方式打开（创建）文件
    if(fp == NULL){
        printf("Cannot open file. quit now. \n");
        return ;
    }
    printf("filename: %s\n", filename);

    // recv datum in a loop until tranport successfully
    char buffer[STRLEN] = {0}; // file buffer
    int nCount;
    while( (nCount = recv(clntSock, buffer, STRLEN, 0)) > 0 ){
        fwrite(buffer, nCount, 1, fp);
    }

    fclose(fp); // close file
    closesocket(clntSock);
    closesocket(servSock);
    printf("success recv successfully. quit soon.\n");
    
    return ;
}

void sendFile(SOCKET linksock) {
    SOCKET  lsock = linksock;
    char    wBuf[STRLEN];
    char    rBuf[STRLEN];
    int     messageLen;
    int     recvlen;
    char    IP[20];
    char    port[20];

    // recv dest port
    memset(rBuf, 0, sizeof(rBuf)); printf("1\n");
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("port recv error. \n");
        return ;
    } else if (recvlen > 0) {
        rBuf[recvlen] = '\0';
        // get port
        strncpy(port, rBuf, strlen(rBuf) + 1);
    }printf("%s\n", port);

    // recv dest ip address
    memset(rBuf, 0, sizeof(rBuf)); 
    recvlen = recv(lsock, rBuf, sizeof(rBuf), 0);
    if (recvlen == 0 || recvlen == SOCKET_ERROR) {
        printf("ip recv error. \n");
        return ;
    } else if (recvlen > 0) {
        rBuf[recvlen] = '\0';
        // get ip
        strncpy(IP, rBuf, strlen(rBuf) + 1);
    }

    // file client socket build
    WSADATA wsaData;
    WSAStartup(WSVERS, &wsaData);
    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.s_addr = inet_addr(IP);
    sockAddr.sin_port = htons(atol(port));
    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    printf("connected. ");
    printf("waitting for filename to send...\n");

    // input filename and test whether it exits or not
    char filename[STRLEN] = {0};
    printf("Input filename to read: ");
    fgets(filename, STRLEN, stdin);
    int len = strlen(filename);
    filename[len - 1] = '\0';
    FILE *fp = fopen(filename, "rb");  //read file by binary
    if(fp == NULL){
        printf("Cannot open file, press any key to exit!\n");
        system("pause");
        return ;
    }
    printf("filename: %s\n", filename);

    // recv datum in a loop until file end
    char buffer[STRLEN] = {0};  // file buffer
    int nCount;
    while( (nCount = fread(buffer, 1, STRLEN, fp)) > 0 ){
        send(sock, buffer, nCount, 0);
    }

    shutdown(sock, SD_SEND); // end to read a file, close output stream, send FIN packet to client
    recv(sock, buffer, STRLEN, 0); // block to wait for client to finish recv file

    fclose(fp);
    closesocket(sock);
    printf("success send successfully. quit soon.\n");

    return ;
}



